package org.dromara.business.config;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import org.dromara.business.contracts.StakingVaultNative;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.gas.ContractGasProvider;
import org.web3j.tx.gas.StaticGasProvider;

import java.math.BigInteger;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;

@Slf4j
@Data
@Configuration
@EnableRetry
@EnableAsync
@ConfigurationProperties(prefix = "web3j")
public class Web3jConfig {

    private String clientAddress;
    private Boolean adminClient = false;
    private Integer networkId = 1;
    private Long gasPrice = 20000000000L;
    private Long gasLimit = 6721975L;
    private String private_key = "0xb4c35e376ff986b5a525d778aca9397da0bd38e688833ffcc95db0aea12906f6";
    private Contracts contracts;
    private Monitor monitor;
    private Connection connection;

    @Data
    public static class Monitor {
        private boolean enabled = true;
        private int batchSize = 50;
        private long pollInterval = 10000;
        private int maxRetryAttempts = 3;
        private long retryDelay = 2000;
    }

    @Data
    public static class Contracts {
        private String addresses;
    }

    @Data
    public static class Connection {
        private int connectTimeoutSeconds = 30;
        private int readTimeoutSeconds = 60;
        private int writeTimeoutSeconds = 60;
        private int maxIdleConnections = 20;
        private int keepAliveDurationMinutes = 5;
        private boolean enableLogging = false;
        private int maxRetryAttempts = 3;
        private long retryDelayMs = 1000;
        private String[] fallbackUrls = {};
    }

    @Bean
    public RetryTemplate retryTemplate() {
        Connection connConfig = connection != null ? connection : new Connection();

        RetryTemplate retryTemplate = new RetryTemplate();

        // 设置重试策略
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        retryPolicy.setMaxAttempts(connConfig.maxRetryAttempts + 1);
        retryTemplate.setRetryPolicy(retryPolicy);

        // 设置指数退避策略
        ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
        backOffPolicy.setInitialInterval(connConfig.retryDelayMs);
        backOffPolicy.setMultiplier(2.0);
        backOffPolicy.setMaxInterval(connConfig.retryDelayMs * 8);
        retryTemplate.setBackOffPolicy(backOffPolicy);

        return retryTemplate;
    }


    @Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(8);
        executor.setQueueCapacity(50);
        executor.setThreadNamePrefix("task-async-");
        executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }


    @Bean
    public Executor web3jTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(16);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("web3j-async-");
        executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    @Bean
    public Web3j web3j() {
        Connection connConfig = connection != null ? connection : new Connection();

        OkHttpClient.Builder builder = new OkHttpClient.Builder();

        // 连接配置优化
        builder.connectTimeout(connConfig.connectTimeoutSeconds, TimeUnit.SECONDS);
        builder.readTimeout(connConfig.readTimeoutSeconds, TimeUnit.SECONDS);
        builder.writeTimeout(connConfig.writeTimeoutSeconds, TimeUnit.SECONDS);

        // 连接池配置
        ConnectionPool connectionPool = new ConnectionPool(
            connConfig.maxIdleConnections,
            connConfig.keepAliveDurationMinutes,
            TimeUnit.MINUTES
        );
        builder.connectionPool(connectionPool);

        // 启用HTTP/2
        builder.protocols(java.util.Arrays.asList(
            okhttp3.Protocol.HTTP_2,
            okhttp3.Protocol.HTTP_1_1
        ));

        // 添加日志拦截器（开发环境）
        if (connConfig.enableLogging) {
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
            builder.addInterceptor(loggingInterceptor);
        }

        // 简化的拦截器，不包含阻塞重试逻辑
        builder.addInterceptor(chain -> {
            okhttp3.Request request = chain.request();
            try {
                okhttp3.Response response = chain.proceed(request);
                if (!response.isSuccessful()) {
                    log.debug("Web3j request returned non-successful status: {}", response.code());
                }
                return response;
            } catch (Exception e) {
                log.debug("Web3j request failed: {}", e.getMessage());
                throw e;
            }
        });

        OkHttpClient httpClient = builder.build();
        HttpService httpService = new HttpService(clientAddress, httpClient, false);

        Web3j web3j = Web3j.build(httpService);

        log.info("Web3j client initialized with address: {}", clientAddress);
        log.info("Network ID: {} ({})", networkId, getNetworkName());
        log.info("Connection pool - Max idle: {}, Keep alive: {} minutes",
                 connConfig.maxIdleConnections, connConfig.keepAliveDurationMinutes);

        // 异步验证连接（使用专用线程池和重试机制）
        verifyConnectionAsync(web3j, connConfig).exceptionally(throwable -> {
            log.warn("Connection verification failed after retries: {}", throwable.getMessage());
            return null;
        });

        return web3j;
    }

    @Async("web3jTaskExecutor")
    @Retryable(
        retryFor = {Exception.class},
        maxAttempts = 3,
        backoff = @Backoff(delay = 1000, multiplier = 2)
    )
    public CompletableFuture<String> verifyConnectionAsync(Web3j web3j, Connection connConfig) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                String clientVersion = web3j.web3ClientVersion().send().getWeb3ClientVersion();
                log.info("Successfully connected to client: {}", clientVersion);
                return clientVersion;
            } catch (Exception e) {
                log.error("Failed to verify Web3j connection: {}", e.getMessage());
                tryFallbackUrls(connConfig);
                throw new RuntimeException("Web3j connection verification failed", e);
            }
        }, web3jTaskExecutor());
    }

    private void tryFallbackUrls(Connection connConfig) {
        if (connConfig.fallbackUrls != null && connConfig.fallbackUrls.length > 0) {
            log.info("Attempting to use fallback URLs: {}",
                     java.util.Arrays.toString(connConfig.fallbackUrls));
            // 可以实现备用URL的逻辑
        }
    }

    @Async("web3jTaskExecutor")
    @Retryable(
        retryFor = {Exception.class},
        maxAttempts = 3,
        backoff = @Backoff(delay = 1000, multiplier = 2)
    )
    public <T> CompletableFuture<T> executeWithRetryAsync(java.util.function.Supplier<T> operation, String operationName) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                log.debug("Executing Web3j operation: {}", operationName);
                T result = operation.get();
                log.debug("Successfully completed Web3j operation: {}", operationName);
                return result;
            } catch (Exception e) {
                log.error("Web3j operation failed [{}]: {}", operationName, e.getMessage());
                throw new RuntimeException("Web3j operation failed: " + operationName, e);
            }
        }, web3jTaskExecutor());
    }

    @Bean
    public Credentials credentials() {
        return Credentials.create(private_key);
    }

    @Bean
    public ContractGasProvider contractGasProvider() {
        return new StaticGasProvider(BigInteger.valueOf(gasPrice), BigInteger.valueOf(gasLimit));
    }

    @Bean
    public StakingVaultNative stakingVaultNative(Web3j web3j, Credentials credentials, ContractGasProvider gasProvider) {
        return StakingVaultNative.load(getContracts().getAddresses(), web3j, credentials, gasProvider);
    }

    public String getNetworkName() {
        return switch (networkId) {
            case 1 -> "Ethereum Mainnet";
            case 3 -> "Ropsten Testnet";
            case 4 -> "Rinkeby Testnet";
            case 5 -> "Goerli Testnet";
            case 11155111 -> "Sepolia Testnet";
            case 56 -> "BSC Mainnet";
            case 97 -> "BSC Testnet";
            case 137 -> "Polygon Mainnet";
            case 80001 -> "Mumbai Testnet";
            default -> "Unknown Network";
        };
    }
}
